/*
 * File:   Node.cpp
 * Author: henrya
 *
 * Created on June 16, 2009, 3:05 PM
 */

#include "Node.h"
#include "Tokenizer.h"
#include "Network.h"
#include "Shell.h"
#include "RoutingMessage.h"
#include "RuntimeException.h"
#include "SystemMessage.h"
#include "SimpleMessage.h"
#include <cstdlib>
#include <iostream>
#include <sstream>
using std::stringstream;
using std::endl;

Node::Node(Shell* shell, Mailer* mailer, Network* network, int id) : network(network), id(id), interrupted(false), mailer(mailer),shell(shell) {
}

Node::~Node() {
    if (routingTable != NULL) {
        delete routingTable;
    }
}

void Node::addNeighbour(int id) {
    neighboursIds.push_back(id);
}

Node* Node::createNode(Shell* shell, Mailer* mailer, Network* network, string& nodeConf) {//assuming correct node configuration
    Tokenizer nodeTknzr(nodeConf);
    int id = atoi((*nodeTknzr.next()).c_str());
    Node* res = new Node(shell,mailer, network, id);
    while (nodeTknzr.hasNext()) {
        int neigbourId = atoi((*nodeTknzr.next()).c_str());
        res->addNeighbour(neigbourId);
    }
    return res;
}

const int Node::getId() {
    return id;
}

void Node::run() {
    yield();
    routingTable = new RoutingTable(id, network->getNodeIds());
    sendRTToAllNeigbours();
    while (!interrupted) {
        Message* recieved;
        while (!recv((void**) & recieved)) {
            yield();
        }
        if (recieved != NULL) {
            switch (recieved->getType()) {
            case Message::SYSTEM : {
                SystemMessage::MessageType* type=(SystemMessage::MessageType*) recieved->getMessage();
                switch (*type) {
                case SystemMessage::KILL_NODE: {
                    int nodeKilled=recieved->getFrom();
                    if (nodeKilled==id) {
                        vector<int>* allNodes=network->getNodeIds();
                        for (vector<int>::iterator itr = allNodes->begin(); itr != allNodes->end(); itr++) {
                            Message* toSend = new SystemMessage(SystemMessage::KILL_NODE);
                            toSend->setTo(*itr);
                            while (!send(toSend)) {
                                yield();
                            }
                        }
                        //todo sleep here
                    } else {
                        for (vector<int>::iterator itr = neighboursIds.begin(); itr != neighboursIds.end(); itr++) {
                            if (*itr==recieved->getFrom()) {
                                neighboursIds.erase(itr);
                                killedIds.push_back(*itr);
                                break;
                            }
                        }
                        resetRT();
                        sendRTToAllNeigbours();
                    }
                }
                break;
                case SystemMessage::PRINT_RT: {
                    Message* msg=new SystemMessage(SystemMessage::PRINT_RT,routingTable->printRT());
                    msg->setFrom(id);
                    while (!send(msg)) {
                        yield();
                    }
                }
                break;
                case SystemMessage::REVIVE_NODE: {
                    int nodeRevived=recieved->getFrom();
                    if (nodeRevived==id) {
                        for (vector<int>::iterator itr = neighboursIds.begin(); itr != neighboursIds.end(); itr++) {
                            Message* toSend = new SystemMessage(SystemMessage::REVIVE_NODE);
                            toSend->setFrom(nodeRevived);
                            toSend->setTo(*itr);
                            while (!send(toSend)) {
                                yield();
                            }
                        }
                        resetRT();
                        sendRTToAllNeigbours();
                    } else {
                        for (vector<int>::iterator itr = killedIds.begin(); itr != killedIds.end(); itr++) {
                            if (*itr==recieved->getFrom()) {
                                killedIds.erase(itr);
                                neighboursIds.push_back(*itr);
                                break;
                            }
                        }
                    }
                }
                break;
                case SystemMessage::SEND_PACKET: {
                    string* msgText=new string(*(string*)((SystemMessage*)recieved)->getAdditionalData(),0,recieved->getFrom());
                    Message* msg=new SimpleMessage(msgText);
                    msg->setFinalDest(recieved->getFinalDest());
                    int sendThro=routingTable->getNextOnPath(recieved->getFinalDest());
                    msg->setTo(sendThro);
                    msg->setFrom(id);
                    stringstream ss;
                    ss<<id<<" is sending "<<*msgText<<" to "<<msg->getFinalDest()<<" through "<<sendThro<<","<<msg->getCurrentHop();
                    shell->print(new string(ss.str()));
                    while (!send(msg)) {
                        yield();
                    }
                }
                break;
                }
            }
            break;
            case Message::ROUTING : {
                RoutingTable* updatedTable = (RoutingTable *) recieved->getMessage();
                cout<<"recieved routing msg from "<<recieved->getFrom()<<endl<<*updatedTable->printRT()<<endl;
                if (updatedTable != NULL && routingTable->update(*updatedTable)) {
                    sendRTToAllNeigbours();
                }
            }
            break;
            case Message::SIMPLE : {
                string* msgText=(string*)recieved->getMessage();
                if (recieved->getFinalDest()!=id) {
                    int sendThro=routingTable->getNextOnPath(recieved->getFinalDest());
                    recieved->setFrom(id);
                    recieved->setTo(sendThro);
                    stringstream ss;
                    ss<<id<<" is sending "<<*msgText<<" to "<<recieved->getFinalDest()<<" through "<<sendThro<<","<<recieved->getCurrentHop();
                    shell->print(new string(ss.str()));
                    while (!send(recieved)) {
                        yield();
                    }
                }else{
                    stringstream ss;
                    ss<<id<<" recieved "<<*msgText<<" from "<<recieved->getFrom()<<" after "<<recieved->getCurrentHop()<<"steps";
                    shell->print(new string(ss.str()));
                }
            }
            break;
            }
        } else {
            RuntimeException ex("BUG: in node recieve computation");
            throw ex;
        }
    }
}

bool Node::studentSend(void *args) {
    Message* toSend = (Message*) args;
    bool res=mailer->tryAddMessage(toSend);
    if (res) {
        cout<<"sended msg to "<<toSend->getTo()<<endl;
    }
    return res;
}

bool Node::studentRecv(void **args) {
    return (*args = inbox.pop()) != NULL;
}

string* Node::printRoutingTable() {
    return routingTable->printRT();
}

void Node::addMessage(Message* msg) {//only mailer should use this method
    inbox.push(msg);
}

void Node::sendToNeigbour(int neigbourId, Message* msg) {
    msg->setFrom(id);
    msg->setTo(neigbourId);
    while (!send(msg)) {
        yield();
    }
}

void Node::sendRTToAllNeigbours() {
    static unsigned int rtVersion(1);
    routingTable->setVersion(rtVersion++);
    for (vector<int>::iterator itr = neighboursIds.begin(); itr != neighboursIds.end(); itr++) {
        Message* toSend = new RoutingMessage(routingTable);
        cout<<"This is copy: "<<*((RoutingTable*)(toSend->getMessage()))->printRT()<<endl;
        sendToNeigbour(*itr, toSend);
    }
}
//warning , cast from int to unsigned int
unsigned int Node::getInboxSize() {
    return inbox.size();
}

void Node::resetRT() {
    delete routingTable;
    routingTable = new RoutingTable(id, network->getNodeIds());
}

